package library.RemoteControl;

import java.lang.reflect.InvocationTargetException;

import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Toolkit;
import javax.swing.ImageIcon;
import javax.swing.SwingUtilities;
        
import org.apache.log4j.*;

import library.Forms.FormInterface;
import library.Thread.*;
import library.Logs.*;

/**
 * Class_FrameRemoteControl defines a remote control that transmits all
 * graphical commands for Java Application or Applet for example.<br/>
 * When those methods update graphical components, they should be 
 * executed in the EDT thread, and then invoked through SwingUtilities.invokeAndWait.<br/>
 * @author JNC
 */
public class FrameRemoteControl implements RemoteControlInterface {

    /**
     * The log4j logger.
     */
     private static final Logger _logger = Logger.getLogger(FrameRemoteControl.class);
     
    /**
     * The frame to be accessed by the thread.
     */
    private FormInterface _framedObject;
    
    /**
     * The thread that will interact with the frame.
     */
    private ProcessingThread _processingThread;
    
    /**
     * The log area with auto scroll down capability
     */
    private TextAreaAppender _logArea;
    
    /**
     * Constructor.
     */
    public FrameRemoteControl() {
        _framedObject = null;
        _processingThread = null;
    }
    
    /**
     * Registers an object in order to let objects interact together through 
     * the remote control. <br/>
     * This method is not designed to run in the EDT as it commands no graphical
     * update.
     * @param myObject
     * The object to unregister
     * @throws RemoteControl.Exception_UnknownClassException
     * Exception thrown when the object to register is not known
     */
    public void register (Object myObject) throws UnknownClassException {
        if (myObject instanceof FormInterface) {
            _logger.debug("Registering an Interface_Form");
            _framedObject = (FormInterface)myObject;
            
        } else if (myObject instanceof ProcessingThread) {
            _logger.debug("Registering a Class_ProcessingThread");
            _processingThread = (ProcessingThread)myObject;
            
        } else if (myObject instanceof TextAreaAppender) {
            _logger.debug("Registering a Class_TextAreaAppender : " + myObject);
            _logArea = (TextAreaAppender)myObject;
            
        } else {
            _logger.error("Unable to registrer the object(" + myObject.getClass().getName() + ")" );
            throw new UnknownClassException("Unknown object " + myObject.getClass().getName());
        }
    }
    
    /**
     * Unregisters an object from the remote control.<br/>
     * This method is not designed to run in the EDT as it commands no graphical
     * update.
     * @param myObject the object to unregister
     * @throws RemoteControl.Exception_UnknownClassException This exception is 
     * thrown when the object to unregister is not known
     */
    public void unregister(Object myObject) throws UnknownClassException {
        if (myObject instanceof FormInterface) {
            _logger.debug("Unregistering an Interface_Form object");
            _framedObject = null;
            
        } else if (myObject instanceof ProcessingThread) {
            _logger.debug("Unregistering a Class_ProcessingThread object");
            _processingThread = null;
            
        } else if (myObject instanceof TextAreaAppender) {
            _logger.debug("Unregistering a Class_TextAreaAppender");
            _logArea = null;
            
        } else {
            _logger.error("Unable to unregistrer the object(" + myObject.getClass().getName() + ")" );
            throw new UnknownClassException("Unknown object " + myObject.getClass().getName());
        }        
    }
    
    /**
     * Enables/disables all the commands of the frame. <br/>
     * Also sets the text of the button to GO (not CANCEL any longer). <br/>
     * This method ensures that the commands updating graphical components are
     * run in the EDT. <br/>
     * @param myMustBeEnabled
     * Indicates whether the commands must be enabled (true) or disabled (false)
     */
    public void enableCommands(boolean myMustBeEnabled) {
           
        // Defines a class Runnable code to start the action in the EDT
        // This action can not be started instantiating a new Runnable directly
        // in SwingUtilities.invokeAndWait, as there is an argument to use
        class RunnableCode implements Runnable {
            boolean _mustBeEnabled;
            public RunnableCode(boolean myMustBeEnabled) {
                _mustBeEnabled = myMustBeEnabled;
            }
            public void run() {               
                _framedObject.enableCommands(_mustBeEnabled);
            }
        }
        
        // if the method is called from the EDT, just run the code
        // otherwise, run the code in the EDT
        if (SwingUtilities.isEventDispatchThread()) {
            // the current thread is the EDT
            new RunnableCode(myMustBeEnabled).run();
        } else {
            // the current thread is not the EDT, so run the code in the EDT
            try {
                SwingUtilities.invokeAndWait(new RunnableCode(myMustBeEnabled));            
            }
            catch (InterruptedException exception) {
                _logger.error("InterruptedException caught during InvokeLater : " + exception);
            }
            catch (InvocationTargetException exception) {
                _logger.error("InvocationTargetException caught while invokeAndWait : " + exception);
            } 
        }
    }
     
    /**
     * Starts the progress bar in indeterminate mode. <br/>
     * This method ensures that the commands updating graphical components are
     * run in the EDT. <br/>
     */
    public void startIndeterminateProgressBar() {       
        // Creates a runnable code, in order to have a single code to run whether
        // the thread is the EDT or not
        Runnable code = new Runnable() {
            public void run() {              
                _framedObject.setProgressBarString("Listing the files to process...");
                _framedObject.setProgressBarStringPainted(true);
                _framedObject.setProgressBarIndeterminate(true);                
            }
        };
        
        // if the method is called from the EDT, just run the code
        // otherwise, run the code in the EDT
        if (SwingUtilities.isEventDispatchThread()) {
            // the current thread is the EDT
            code.run();
        } else {
            // the current thread is not the EDT
            try {
                SwingUtilities.invokeAndWait(code);            
            }
            catch (InterruptedException exception) {
                _logger.error("InterruptedException caught during InvokeLater : " + exception);
            }
            catch (InvocationTargetException exception) {
                _logger.error("InvocationTargetException caught while invokeAndWait : " + exception);
            }            
        }
    }
    
    /**
     * Stops the progress bar from determinate mode.<br/>
     * This method ensures that the commands updating graphical components are
     * run in the EDT. <br/>
     */
    public void stopIndeterminateProgressBar() {       
        // Creates a runnable code, in order to have a single code to run whether
        // the thread is the EDT or not
        Runnable code = new Runnable() {
            public void run() {               
                _framedObject.setProgressBarString(null);
                _framedObject.setProgressBarStringPainted(false);
                _framedObject.setProgressBarIndeterminate(false);             
            }
        };
        
        // if the method is called from the EDT, just run the code
        // otherwise, run the code in the EDT
        if (SwingUtilities.isEventDispatchThread()) {
            // the current thread is the EDT
            code.run();
        } else {
            // the current thread is not the EDT
            try {
                SwingUtilities.invokeAndWait(code);            
            }
            catch (InterruptedException exception) {
                _logger.error("InterruptedException caught during InvokeLater : " + exception);
            }
            catch (InvocationTargetException exception) {
                _logger.error("InvocationTargetException caught while invokeAndWait : " + exception);
            }            
        }
    }
    
    /**
     * Initializes the progress bar for determinate mode.<br/>
     * This method ensures that the commands updating graphical components are
     * run in the EDT. <br/>
     */
    public void initDeterminateProgressBar() {           
        // Creates a runnable code, in order to have a single code to run whether
        // the thread is the EDT or not
        Runnable code = new Runnable() {
            public void run() {                
                _framedObject.setProgressBarIndeterminate(false);
                _framedObject.setProgressBarStringPainted(true);            
            }
        };
        
        // if the method is called from the EDT, just run the code
        // otherwise, run the code in the EDT
        if (SwingUtilities.isEventDispatchThread()) {
            // the current thread is the EDT
            code.run();
        } else {
            // the current thread is not the EDT
            try {
                SwingUtilities.invokeAndWait(code);            
            }
            catch (InterruptedException exception) {
                _logger.error("InterruptedException caught during InvokeLater : " + exception);
            }
            catch (InvocationTargetException exception) {
                _logger.error("InvocationTargetException caught while invokeAndWait : " + exception);
            }            
        }
    }

    /** 
     * Sets a progress to the progress bar. <br/>
     * This method ensures that the commands updating graphical components are
     * run in the EDT. <br/>
     * @param myProgress
     * Integer from 0 to 100 showing the current progress
     */
    public void setDeterminateProgressBar(int myProgress) {        
        // Defines a class Runnable code to start the action in the EDT
        // This action can not be started instantiating a new Runnable directly
        // in SwingUtilities.invokeAndWait, as there is an argument to use
        class RunnableCode implements Runnable {
            int _progress;
            public RunnableCode(int myProgress) {
                _progress = myProgress;
            }
            public void run() {
                _framedObject.setProgressBarValue(_progress);
                _framedObject.setProgressBarString("Progress " + _progress + "%"); 
            }
        }
        
        // if the method is called from the EDT, just run the code
        // otherwise, run the code in the EDT
        if (SwingUtilities.isEventDispatchThread()) {
            // the current thread is the EDT
            new RunnableCode(myProgress).run();
        } else {
            // the current thread is not the EDT, so run the code in the EDT
            try {
                SwingUtilities.invokeAndWait(new RunnableCode(myProgress));            
            }
            catch (InterruptedException exception) {
                _logger.error("InterruptedException caught during InvokeLater : " + exception);
            }
            catch (InvocationTargetException exception) {
                _logger.error("InvocationTargetException caught while invokeAndWait : " + exception);
            }             
        }
    }
    
    /**
     * Stops the progress bar from indeterminate mode. <br/>
     * This method ensures that the commands updating graphical components are
     * run in the EDT. <br/>
     */    
    public void stopDeterminateProgressBar() {
        // Creates a runnable code, in order to have a single code to run whether
        // the thread is the EDT or not
        Runnable code = new Runnable() {
            public void run() {
                _framedObject.setProgressBarIndeterminate(false);
                _framedObject.setProgressBarString(null);
                _framedObject.setProgressBarStringPainted(true);         
            }
        };
        
        // if the method is called from the EDT, just run the code
        // otherwise, run the code in the EDT
        if (SwingUtilities.isEventDispatchThread()) {
            // the current thread is the EDT
            code.run();
        } else {
            // the current thread is not the EDT
            try {
                SwingUtilities.invokeAndWait(code);            
            }
            catch (InterruptedException exception) {
                _logger.error("InterruptedException caught during InvokeLater : " + exception);
            }
            catch (InvocationTargetException exception) {
                _logger.error("InvocationTargetException caught while invokeAndWait : " + exception);
            }               
        }
    }

    /**
     * Reinits the progress bar <br/>
     * This method ensures that the commands updating graphical components are
     * run in the EDT. <br/>
     */
    public void reinitProgressBar() {
        // Creates a runnable code, in order to have a single code to run whether
        // the thread is the EDT or not
        Runnable code = new Runnable() {
            public void run() {
                _framedObject.reinitProgressBar();        
            }
        };
        
        // if the method is called from the EDT, just run the code
        // otherwise, run the code in the EDT
        if (SwingUtilities.isEventDispatchThread()) {
            // the current thread is the EDT
            code.run();
        } else {
            // the current thread is not the EDT
            try {
                SwingUtilities.invokeAndWait(code);            
            }
            catch (InterruptedException exception) {
                _logger.error("InterruptedException caught during InvokeLater : " + exception);
            }
            catch (InvocationTargetException exception) {
                _logger.error("InvocationTargetException caught while invokeAndWait : " + exception);
            }              
        }
    }
    
    /**
     * Sets all labels referencing a given jpeg file and request the picture to
     * be displayed.<br/>
     * This method ensures that the commands updating graphical components are
     * run in the EDT. <br/>
     * @param myNameBefore
     * The name of the file before renaming
     * @param myDigitalizationDate
     * The digitalization date of the file as read in the jpeg EXIF metadata
     * @param myTitle
     * The title of the file as read in the jpeg EXIF metadata
     * @param myNameAfter
     * The name of the file after renaming
     * @param mysPicturePath
     * The path of the jpeg file being processed
     */
    public void setLabelsAndShowPicture(String myNameBefore, String myDigitalizationDate, String myTitle, String myNameAfter, String myPicturePath) {
        // Defines a class Runnable code to start the action in the EDT
        // This action can not be started instantiating a new Runnable directly
        // in SwingUtilities.invokeAndWait, as there is an argument to use
        class RunnableCode implements Runnable {
            String _nameBefore;
            String _digitalizationDate;
            String _title;
            String _nameAfter;
            String _picturePath;
            public RunnableCode(String myNameBefore, String myDigitalizationDate, String myTitle, String myNameAfter, String myPicturePath) {
                _nameBefore = myNameBefore;
                _digitalizationDate = myDigitalizationDate;
                _title = myTitle;
                _nameAfter = myNameAfter;
                _picturePath = myPicturePath;
            }
            public void run() {
                // first sets the labels
                setLabels(_nameBefore, _digitalizationDate, _title, _nameAfter);
        
                // and then requests the thumbnail to be displayed
                showPicture(_picturePath);
            }
        }
        
        // if the method is called from the EDT, just run the code
        // otherwise, run the code in the EDT
        if (SwingUtilities.isEventDispatchThread()) {
            // the current thread is the EDT
            new RunnableCode(myNameBefore, 
                        myDigitalizationDate,
                        myTitle, 
                        myNameAfter, 
                        myPicturePath).run();
        } else {
            // the current thread is not the EDT, so run the code in the EDT  
            try {
                SwingUtilities.invokeAndWait(new RunnableCode(myNameBefore, 
                                            myDigitalizationDate, 
                                            myTitle, 
                                            myNameAfter, 
                                            myPicturePath));            
            }
            catch (InterruptedException exception) {
                _logger.error("InterruptedException caught during InvokeLater : " + exception);
            }
            catch (InvocationTargetException exception) {
                _logger.error("InvocationTargetException caught while invokeAndWait : " + exception);
            }                       
        }
    }
    
    /**
     * Displays a picture in a label of the frame.<br/>
     * This method does not care about being run in the EDT, as this issue
     * is managed by the calling method.<br/>
     * @param mysPicturePath
     * The picture to display. If null, remove the picture.
     */
    private void showPicture(String mysPicturePath) {
        Image scaledImage = null;
        Image image = null;
        MediaTracker tracker = null;
        ImageIcon imageIcon = null;
        boolean bAbort = false;
        int height ;
        int width;
        int proposedHeight;
        int proposedWidth;

        // delete the current image if requested
        // -------------------------------------
        if ((mysPicturePath==null) || ! _framedObject.isShowPicturesCheckBoxSelected()) {
            _framedObject.setShowPicturesLabelIcon(null);
        } else {
        
            // get the image asynchronously
            // ----------------------------
            if (!bAbort) {
                image = Toolkit.getDefaultToolkit().createImage(mysPicturePath);
            }
            
            // wait till the image is loaded
            // -----------------------------
            if (!bAbort) {
                tracker = new MediaTracker(_framedObject.getShowPicturesLabel());
                tracker.addImage(image,0);
                try {
                    tracker.waitForAll();
                }
                catch(InterruptedException exc) {
                    _logger.error("InterruptedException caught while loading files : " + exc);
                    bAbort = true;
                }
                
                // cause the object deallocation
                tracker = null;
            }
            
            // get the image size
            // ------------------
            if (!bAbort) {
                height = image.getHeight(_framedObject.getRootPane());
                width = image.getWidth(_framedObject.getRootPane());               
                
                // resize the image, respecting the longest dimension
                // --------------------------------------------------
                if (height > width) {
                    proposedWidth = (_framedObject.getShowPicturesLabelWidth()*width)/height;
                    proposedHeight = _framedObject.getShowPicturesLabelHeight();
                } else {
                    proposedWidth = _framedObject.getShowPicturesLabelWidth();
                    proposedHeight = (_framedObject.getShowPicturesLabelHeight()*height)/width;            
                }
                
                // rescale the image
                scaledImage = image.getScaledInstance(proposedWidth, proposedHeight, Image.SCALE_FAST);            
                
                // cause the object deallocation
                image = null;
            }
            
            // create an image icon and set it to the JLabel
            // --------------------------------------------
            if (!bAbort) {
                imageIcon = new ImageIcon(scaledImage);        
                
                // cause the object deallocation
                scaledImage = null;
                
                // show the icon
                _framedObject.setShowPicturesLabelIcon(imageIcon);
                
                // cause the object deallocation
                imageIcon = null;
            }
        }     
    }
    
    /**
     * Sets all labels referencing a given jpeg file. <br/>
     * This method does not care about being run in the EDT, as this issue
     * is managed by the calling method.<br/>
     * @param myNameBefore
     * The name of the file before renaming
     * @param myDigitalizationDate
     * The digitalization date of the file as read in the jpeg EXIF metadata
     * @param myTitle
     * The title of the file as read in the jpeg EXIF metadata
     * @param myNameAfter
     * The name of the file after renaming
     */
    private void setLabels(String myNameBefore, String myDigitalizationDate, String myTitle, String myNameAfter) {
        
        if (_framedObject.isShowLabelsCheckBoxSelected()) {
            
            _framedObject.setFileNameBeforeLabelText(myNameBefore);
            _framedObject.setTitleLabelText(myTitle);
            _framedObject.setDigitalizationDateLabelText(myDigitalizationDate);
            _framedObject.setFileNameAfterLabelText(myNameAfter);                
        } else {
            _framedObject.setFileNameBeforeLabelText(null);
            _framedObject.setTitleLabelText(null);
            _framedObject.setDigitalizationDateLabelText(null);
            _framedObject.setFileNameAfterLabelText(null);              
        }
    }
    
    /**
     * Stops the processing thread. <br/>
     * This method is not designed to run in the EDT as it commands no graphical
     * update.<br/>
     */
    public void requestProcessingThreadToStop() {
        _logger.debug("Request the processing thread to stop");        

        if (_processingThread != null) {
            _processingThread.requestToStop();
        }
    }
    
    /**
     * Asks if the processing thread is still alive. <br/>
     * This test is not necessary when running an application (all non-daemon already
     * started thread go on running even if the frame is closed). <br/>
     * However, for applets, all runing threads are stopped at the end of the 
     * stop() method of the applet. In this case, it is necessary to delay 
     * the end of the stop() method up to the effective death of the thread. <br/>
     * This method is not designed to run in the EDT as it commands no graphical
     * update.<br/>
     * @return
     * true if alive, false otherwise
     */
    public boolean isProcessingThreadAlive() {
        boolean returnCode ;
        
        _logger.debug("isProcessingThreadAlive");

        returnCode = false;
        
        if (_processingThread != null) {
            returnCode = _processingThread.isAlive();
        }
        
        return returnCode;
    }    
    
    /**
     * Sets the text of the GO/CANCEL button to GO and activates the button.<br/>
     * This method ensures that the commands updating graphical components are
     * run in the EDT. <br/>
     */
    public void reinitGoCancelButton() {
        // Creates a runnable code, in order to have a single code to run whether
        // the thread is the EDT or not
        Runnable code = new Runnable() {
            public void run() {
                _framedObject.reinitGoCancelButton();
                _framedObject.activateGoCancelButton(true);     
            }
        };
        
        // if the method is called from the EDT, just run the code
        // otherwise, run the code in the EDT
        if (SwingUtilities.isEventDispatchThread()) {
            // the current thread is the EDT
            code.run();
        } else {
            // the current thread is not the EDT
            try {
                SwingUtilities.invokeAndWait(code);            
            }
            catch (InterruptedException exception) {
                _logger.error("InterruptedException caught during InvokeLater : " + exception);
            }
            catch (InvocationTargetException exception) {
                _logger.error("InvocationTargetException caught while invokeAndWait : " + exception);
            }                 
        }
    }
    
    /**
     * Activates/desactivates the GO/CANCEL button.<br/>
     * This method ensures that the commands updating graphical components are
     * run in the EDT. <br/>
     * @param myActivation
     * Indicates if the button must be activated (true) or desactivated (false)
     */
    public void activateGoCancelButton(boolean myActivation) {
        // Defines a class Runnable code to start the action in the EDT
        // This action can not be started instantiating a new Runnable directly
        // in SwingUtilities.invokeAndWait, as there is an argument to use
        class RunnableCode implements Runnable {
            boolean _activation;
            public RunnableCode(boolean myActivation) {
                _activation = myActivation;
            }
            public void run() {
                _framedObject.activateGoCancelButton(_activation);
            }
        }
        
        // if the method is called from the EDT, just run the code
        // otherwise, run the code in the EDT
        if (SwingUtilities.isEventDispatchThread()) {
            // the current thread is the EDT
            new RunnableCode(myActivation).run();
        } else {
            // the current thread is not the EDT, so run the code in the EDT
            try {
                SwingUtilities.invokeAndWait(new RunnableCode(myActivation));            
            }
            catch (InterruptedException exception) {
                _logger.error("InterruptedException caught during InvokeLater : " + exception);
            }
            catch (InvocationTargetException exception) {
                _logger.error("InvocationTargetException caught while invokeAndWait : " + exception);
            }             
        }
    }
    
    /**
     * Enables / disables the auto scroll down capability of the jTextArea. <br/>
     * This method is not designed to run in the EDT as it does not directly
     * command graphical update.<br/>
     * @param myMustActivateScrollDown
     * true to enable scroll down
     * false to disable scroll down
     */
    public void disableAutoScrollDownTemporary() {
        if (_logArea == null) {
            _logger.error("_logArea is null");
        } else {
            _logArea.disableAutoScrollDownTemporary();
        }
    }
    
    /**
     * Change the border of the text area when the auto scroll down is enbaled / disabled
     * This method ensures that the commands updating graphical components are
     * run in the EDT. <br/>
     * @param isTransient
     */
    public void changeBorderToTransient(boolean myIsTransient) {
        
        // This action can not be started instantiating a new Runnable directly
        // in SwingUtilities.invokeAndWait, as there is an argument to use
        class RunnableCode implements Runnable {
            boolean _isTransient;
            public RunnableCode(boolean myIsTransient) {
                _isTransient = myIsTransient;
            }
            public void run() {
                _framedObject.changeBorderToTransient(_isTransient);
            }
        }
        
        // if the method is called from the EDT, just run the code
        // otherwise, run the code in the EDT
        if (SwingUtilities.isEventDispatchThread()) {
            // the current thread is the EDT
            new RunnableCode(myIsTransient).run();
        } else {
            // the current thread is not the EDT, so run the code in the EDT
            try {
                SwingUtilities.invokeAndWait(new RunnableCode(myIsTransient));            
            }
            catch (InterruptedException exception) {
                _logger.error("InterruptedException caught during InvokeLater : " + exception);
            }
            catch (InvocationTargetException exception) {
                _logger.error("InvocationTargetException caught while invokeAndWait : " + exception);
            }             
        }
    }
}
